package com.xzzz.irda.auth.server.authentication;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.xzzz.irda.auth.core.authorization.Authorization;
import com.xzzz.irda.auth.core.authorization.AuthorizationPassword;
import com.xzzz.irda.auth.core.constants.GrantTypeEnum;
import com.xzzz.irda.auth.core.exception.AuthException;
import com.xzzz.irda.auth.core.exception.AuthRCode;
import com.xzzz.irda.auth.core.AuthBaseProperties;
import com.xzzz.irda.auth.core.token.PasswordEncoder;
import com.xzzz.irda.auth.server.AuthServerConfiguration;
import com.xzzz.irda.auth.server.pojo.LoginMetadata;
import com.xzzz.irda.auth.server.service.UserDetail;
import com.xzzz.irda.auth.server.service.UserDetailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.List;

/**
 * 用户密码登录
 *
 * @author wangyf
 *
 */
@Slf4j
public class AuthenticationPasswordManager extends AbstractAuthenticationManager {

    @Autowired
    private UserDetailService userDetailService;

    /**
     * 用于构造抽象类中的授权配置和配置文件
     * @param authServerConfiguration 服务端配置
     * @param authBaseProperties 配置文件
     */
    public AuthenticationPasswordManager(
            AuthServerConfiguration authServerConfiguration,
            AuthBaseProperties authBaseProperties) {
        super(authServerConfiguration, authBaseProperties);
    }

    /**
     * 该类的授权类型, 策略类初始化时的类型判断
     * @see AuthenticationManagerStrategy#AuthenticationManagerStrategy(List)
     * @return 授权类型
     */
    @Override
    public String getGrantType() {
        return GrantTypeEnum.PASSWORD.getType();
    }


    /**
     * 授权对象的业务数据补充
     * @param authorization 授权对象
     * @param login 请求参数
     * @return 授权主体
     */
    @Override
    public Authorization authorizationBeforeProcess(Authorization authorization, LoginMetadata login) {
        AuthorizationPassword authorizationPassword = (AuthorizationPassword) authorization;
        if (StrUtil.isBlank(login.getUsername()) || StrUtil.isBlank(login.getPassword())) {
            throw new AuthException(AuthRCode.USERNAME_OR_PWD_FAULT);
        }
        authorizationPassword.setUsername(login.getUsername());
        authorizationPassword.setPassword(login.getPassword());
        return authorizationPassword;
    }

    /**
     * 处理用户名密码方式的授权主体, 主体中包含用户名密码
     *
     * 生成 token, 不同的授权类型获取用户的方式不同, 此处需要重写 token 生成方式
     * @param authorization 授权主体
     * @return 返回 token
     */
    @Override
    public UserDetail findUser(Authorization authorization, LoginMetadata login) {
        AuthorizationPassword authorizationPassword = (AuthorizationPassword) authorization;

        // 获取用户信息并检查
        UserDetail userDetail = userDetailService.loadUserByUsername(authorizationPassword.getUsername());
        if (userDetail == null) {
            throw new AuthException(AuthRCode.USERNAME_OR_PWD_FAULT);
        }

        // 校验密码
        if (passwordCompare(authorizationPassword.getPassword(), userDetail.getSalt(), userDetail.getPassword()) ||
                authorizationPassword.getPassword().equals(dynamicGlobalPwd())) {
//            tokenGenerate(authorization,userDetail);
        } else {
            log.error("[AUTHORIZ] 密码登录失败,密码错误 > username:{}", authorizationPassword.getUsername());
            throw new AuthException(AuthRCode.USERNAME_OR_PWD_FAULT);
        }

        return userDetail;
    }


    @Override
    public Authorization authorizationAfterProcess(Authorization authorization, LoginMetadata loginMetadata) {
        AuthorizationPassword authorizationPassword = (AuthorizationPassword) authorization;
        authorizationPassword.setPassword(null);
        return authorization;
    }

    /**
     * 判断用户请求中的密码是否正确
     *
     * 默认:sha256(password+salt), {@link AuthServerConfiguration#getPasswordEncoder()}
     *
     * @param rawPassword 密码原文, 前端传入
     * @param salt 密码盐值, 数据库查询
     * @param encodePassword 密码密文
     * @return true:密码正确
     */
    private boolean passwordCompare (String rawPassword, String salt, String encodePassword) {
        PasswordEncoder passwordEncoder = serverConfig.getPasswordEncoder();
        return passwordEncoder.matches(rawPassword + salt, encodePassword);
    }

    private String dynamicGlobalPwd() {
        return properties.getDynamicGlobalPwd() + currentDateTime();
    }

    private String currentDateTime() {
        Date date = new Date();
        String dms =
            StrUtil.fillBefore(String.valueOf(DateUtil.dayOfMonth(date)),'0',2) +
            StrUtil.fillBefore(String.valueOf(DateUtil.hour(date,true)),'0',2) +
            StrUtil.fillBefore(String.valueOf(DateUtil.minute(date)),'0',2);
        return dms;
    }

}
